package effects;


import java.util.List;
import java.util.Random;

import factory.EffectFactory;
import factory.InfluenceRadiusFactory;

import loadsave.Memento;
import loadsave.loadsaveobject.Iterator;
import loadsave.loadsaveobject.LoadSaveObject;
import loadsave.loadsaveobject.LoadSaveObjectComposite;
import loadsave.loadsaveobject.LoadSavePrimitive;
import stat.NonEntityStatList;
import stat.StatList;
import stat.StatType;
import visitors.EffectsVisitor;
import influence_radii.InfluenceRadius;

/**
 * The CombatEffect class is meant to encapsulate
 * another effect as a decorator in order to modify
 * the damage or probability of execution.  This
 * class is necessary in order to provide a
 * container class with distance influence (i.e.,
 * the farther the distance, the less damage and
 * lower probability of success).
 * 
 * The CombatEffect class is in charge of
 * re-computing damage modifiers according to
 * currDistTraveled and attackingStats.  It is also
 * in charge of instantiating a
 * CombatEffectMovementHandler which will take care
 * of interacting an Effect with an entity or trap.
 * 
 * @author DangMH
 *
 */
public class CombatEffect extends Effect {
	private static final int probabilityDenominator = 100;
	
	private int level;
	private Effect effect;
	private int minRange;
	private int maxRange;
	private int currDistTraveled;
	private InfluenceRadius influenceRadius;
	
	/**
	 * Empty constructor
	 */
	public CombatEffect() {
		super();
		this.level = 0;
		this.effect = null;
		this.minRange = 0;
		this.maxRange = 0;
		this.currDistTraveled = 0;
		this.influenceRadius = null;
	}
	
	/**
	 * Parameterized constructor for map model
	 * @param name
	 * Map model name
	 */
	public CombatEffect( String name ) {
		super(name);
	}
	
	public List<StatType> getOutputStats() {
		return effect.getOutputStats();
	}
	
	public int getMinRange() {
		return minRange;
	}
	
	public int getMutator() {
		return effect.getMutator();
	}
	
	/**
	 * Parameterized constructor.  Map model name
	 * comes from the effect object.
	 */
	public CombatEffect( int level, Effect effect, int minRange, int maxRange, StatList attackingStats, InfluenceRadius influenceRadius ) {
		this( effect.getName() );
		this.level = level;
		this.effect = effect;
		effect.setStats( attackingStats );
		this.minRange = minRange;
		this.maxRange = maxRange;
		this.influenceRadius = influenceRadius;
	}
	
	private CombatEffect(CombatEffect toCopy) {
		this(toCopy.level, null, toCopy.minRange, toCopy.maxRange, null, toCopy.influenceRadius.clone());
		effect = toCopy.effect.clone();
	}

	/**
	 * Visitor interface
	 */
	public void accept( EffectsVisitor ev ) {
		ev.visitCombatEffect( this );
	}
	
	/**
	 * Derives the influencing stats based on
	 * probability levels and distance traveled
	 */
	public StatList computeStats() {
		StatList ret = new NonEntityStatList();
		
		if( skillSuccess() ) {
			ret = effect.computeStats();
			
			for( StatType st : getOutputStats() ) {
				ret.addStat( st, statDifferential( ret.getStat( st ) ) );
			}
		}
		
		return ret;
	}
	
	/**
	 * Scales a stat differential based on
	 * skill level and current distance
	 * traveled
	 * @param statList
	 * The statList to obtain root values
	 * @param st
	 * The stat to modify
	 */
	private int statDifferential( int statValue ) {
		return (int)(statValue * (level * 0.05 + 1) / ((double)currDistTraveled + 1) - statValue);
	}
	
	/**
	 * InfluenceRadius accessor
	 */
	public InfluenceRadius getInfluenceRadius() {
		return influenceRadius;
	}
	
	/**
	 * StatsList setter method
	 */
	public void setStats( StatList statList ) {
		effect.setStats( statList );
	}
	
	/**
	 * Derives the percentage change of success of
	 * a combat effect from the value of level.
	 * Values of currDistTraveled ranges from 0 to
	 * maxRange - minRange + 1
	 */
	private int computeChance() {
		
		return ( probabilityDenominator - probabilityDenominator * currDistTraveled / (maxRange - minRange + 1) );
	}
	
	/**
	 * Returns whether or not the skill was a success
	 * based on a random generator and derived probability.
	 */
	private boolean skillSuccess() {
		Random random = new Random();
		
		return random.nextInt( probabilityDenominator ) < computeChance();
	}
	
	/**
	 * public interface for incrementing distance
	 * between propagation tiers
	 */
	public void incrementDistTraveled() {
		++currDistTraveled;
	}

	@Override
	public Memento getMemento() {
		return new Memento() {

			@Override
			public LoadSaveObject getState() {
				LoadSaveObject lso = CombatEffect.super.getMemento().getState();
				Iterator<LoadSaveObject> i = lso.getIterator();
				LoadSaveObjectComposite toReturn = new LoadSaveObjectComposite("", "CombatEffect");
				for (i.init(); i.hasNext(); i.next()) {
					toReturn.add(i.getCurrKey().getValue(), i.getCurrVal());
				}
				toReturn.add("level", new LoadSavePrimitive("level", "int", String.valueOf(level)));
				toReturn.add("minRange", new LoadSavePrimitive("minRange", "int", String.valueOf(minRange)));
				toReturn.add("maxRange", new LoadSavePrimitive("maxRange", "int", String.valueOf(maxRange)));
				toReturn.add("currDistTraveled", new LoadSavePrimitive("currDistTraveled", "int", String.valueOf(currDistTraveled)));
				lso = effect.getMemento().getState();
				lso.setName("effect");
				toReturn.add("effect", lso);
				lso = influenceRadius.getMemento().getState();
				lso.setName("influenceRadius");
				toReturn.add("influenceRadius", lso);
				return toReturn;
			}

			@Override
			public void restoreState(LoadSaveObject lso) {
				CombatEffect.super.getMemento().restoreState(lso);
				level = Integer.valueOf(lso.get("level").getValue());
				minRange = Integer.valueOf(lso.get("minRange").getValue());
				maxRange = Integer.valueOf(lso.get("maxRange").getValue());
				currDistTraveled = Integer.valueOf(lso.get("currDistTraveled").getValue());
				effect = EffectFactory.createEffect(lso.get("effect"));
				influenceRadius = InfluenceRadiusFactory.createInfluenceRadius(lso.get("influenceRadius").getType());
			}
			
		};
	}

	@Override
	public CombatEffect clone() {
		return new CombatEffect(this);
	}
}
